/*
 * DevicesIIC.c
 *
 *      Author: Honrun
 */

#include "gd32e23x.h"
#include "stdio.h"
#include "DevicesIIC.h"

void vIIC0Init(void)
{

}
void vIIC0DeInit(void)
{

}
void vIIC0DMAInit(void)
{

}

void vIIC1Init(void)
{
    /* enable GPIOA clock */
    rcu_periph_clock_enable(RCU_GPIOA);
    /* enable I2C1 clock */
    rcu_periph_clock_enable(RCU_I2C1);


    /* configure GPIO pins of I2C1 */
    gpio_af_set(GPIOA, GPIO_AF_1, GPIO_PIN_0);
    gpio_af_set(GPIOA, GPIO_AF_1, GPIO_PIN_1);
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_0);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_0);
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_1);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_1);


    /* configure I2C1 clock */
    i2c_clock_config(I2C1, 100000, I2C_DTCY_2);
    /* configure I2C1 address */
    i2c_mode_addr_config(I2C1, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, 0);
    /* enable I2C1 */
    i2c_enable(I2C1);
    /* enable acknowledge */
    i2c_ack_config(I2C1, I2C_ACK_ENABLE);


    vIIC1DMAInit();


    /* enable I2C1 */
    i2c_enable(I2C1);
}

/*!
    \brief      reset i2c bus
    \param[in]  none
    \param[out] none
    \retval     none
*/
void vIIC1DeInit(void)
{
    /* configure SDA/SCL for GPIO */
    GPIO_BC(GPIOB) |= GPIO_PIN_0;
    GPIO_BC(GPIOA) |= GPIO_PIN_1;
    gpio_mode_set(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_0);
    gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0);
    gpio_mode_set(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_1);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1);
    __NOP();
    __NOP();
    __NOP();
    __NOP();
    __NOP();
    /* stop signal */
    GPIO_BOP(GPIOB) |= GPIO_PIN_0;
    __NOP();
    __NOP();
    __NOP();
    __NOP();
    __NOP();
    GPIO_BOP(GPIOA) |= GPIO_PIN_1;
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_0);
    gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_0);
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_1);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_1);
}

void vIIC1DMAInit(void)
{
    dma_parameter_struct dma_init_struct = {0};
    uint8_t ucDMASendByte = 0, ucDMAReadByte = 0;

    dma_deinit(DMA_CH1);
    dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;
    dma_init_struct.memory_addr = (uint32_t)&ucDMASendByte;
    dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;
    dma_init_struct.number = 0;
    dma_init_struct.periph_addr = (uint32_t)&I2C_DATA(I2C1);
    dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
    dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
    dma_init(DMA_CH1, &dma_init_struct);
    /* enable I2CX DMA */
    i2c_dma_enable(I2C1, I2C_DMA_ON);
    /* enable DMA0 channel1 */
    dma_channel_disable(DMA_CH1);


    /* more than one byte master reception procedure (DMA) */
    dma_deinit(DMA_CH2);
    dma_init_struct.direction = DMA_PERIPHERAL_TO_MEMORY;
    dma_init_struct.memory_addr = (uint32_t)&ucDMAReadByte;
    dma_init_struct.periph_addr = (uint32_t)&I2C_DATA(I2C1);
    dma_init(DMA_CH2, &dma_init_struct);
    /* enable I2CX DMA */
    i2c_dma_enable(I2C1, I2C_DMA_ON);
    /* enable DMA0 channel2 */
    dma_channel_disable(DMA_CH2);
}



/**
 * @brief  设置DMA的数据传输长度，并启动DMA传输
 * @param  DMAy_Channelx：   相应DMA的通道
           Length：          传输长度
 * @retval 无
 */
static void vDMASetAddrAndLenthStart(dma_channel_enum channelx, int8_t cDirection, uint32_t uiMemoryAddress, uint32_t uiPeriphAddress, uint32_t uiLength)
{
    uint32_t uiTime = 0;

    uiTime = 1000000;
    /* Disable the peripheral */
    dma_channel_disable(channelx);
    while(((DMA_CHCTL(channelx) & DMA_CHXCTL_CHEN) != 0) && (--uiTime));

    /* 必须要清除所有中断状态，才允许使能DMA */
    dma_interrupt_flag_clear(channelx, 0xFFFFFFFF);

    /* configure memory base address */
    dma_memory_address_config(channelx, uiMemoryAddress);

    /* configure peripheral base address */
    dma_periph_address_config(channelx, uiPeriphAddress);

    /* Configure DMA Stream data length */
    dma_transfer_number_config(channelx, uiLength);

    /* configure the direction of data transfer on the channel   */
    dma_transfer_direction_config(channelx, cDirection);

    uiTime = 1000000;
    /* Enable the Peripheral */
    dma_channel_enable(channelx);
    while(((DMA_CHCTL(channelx) & DMA_CHXCTL_CHEN) == 0) && (--uiTime));
}


/*!
    \brief      write more than one byte to the EEPROM with a single write cycle
    \param[in]  p_buffer: pointer to the buffer containing the data to be written to the EEPROM
    \param[in]  write_address: EEPROM's internal address to write to
    \param[in]  number_of_byte: number of bytes to write to the EEPROM
    \param[out] none
    \retval     none
*/
int8_t cI2CxDMAWriteDatas(uint32_t i2c_periph, uint16_t usIDAddr, uint16_t usRegAddr, uint8_t* pBuffer, int32_t iLength)
{
    void (*vI2CxBusReset)(void) = NULL;
    dma_channel_enum channelx;
    int32_t timeout = 0, startCnt = 0;

    if(iLength <= 0)
        return -1;

    vI2CxBusReset = (i2c_periph == I2C0) ? vIIC0DeInit : vIIC1DeInit;

    /* i2c master sends start signal only when the bus is idle */
    for(timeout = 0; i2c_flag_get(i2c_periph, I2C_FLAG_I2CBSY) != RESET; ++timeout)
    {
        if(timeout > I2C_BUSY_TIME_OUT)
        {
            vI2CxBusReset();
            printf("cI2C %08X busy.\r\n", i2c_periph);
            return -2;
        }
    }

    /* 重试 N 次 */
    for(startCnt = 0; startCnt < I2C_START_OUT; ++startCnt)
    {
        if(startCnt >= I2C_START_OUT)
        {
            vI2CxBusReset();
            printf("cI2C %08X start timeout.\r\n", i2c_periph);
            return -3;
        }

        i2c_start_on_bus(i2c_periph);
        /* i2c master sends START signal successfully */
        for(timeout = 0; timeout < I2C_BYTE_TIME_OUT; ++timeout)
        {
            if(i2c_flag_get(i2c_periph, I2C_FLAG_SBSEND) != RESET)
            {
                break;
            }
        }

        /* configure slave address */
        i2c_master_addressing(i2c_periph, usIDAddr, I2C_TRANSMITTER);
        /* address flag set means i2c slave sends ACK */
        for(timeout = 0; timeout < I2C_BYTE_TIME_OUT; ++timeout)
        {
            if(i2c_flag_get(i2c_periph, I2C_FLAG_ADDSEND) != RESET)
            {
                i2c_flag_clear(i2c_periph, I2C_FLAG_ADDSEND);
                break;
            }
        }

        /* wait until the transmit data buffer is empty */
        for(timeout = 0; timeout < I2C_BYTE_TIME_OUT; ++timeout)
        {
            if(i2c_flag_get(i2c_periph, I2C_FLAG_TBE) != RESET)
            {
                break;
            }
        }

        /* send address */
        i2c_data_transmit(i2c_periph, usRegAddr);
        /* wait until the transmit data buffer is empty */
        for(timeout = 0; timeout < I2C_BYTE_TIME_OUT; ++timeout)
        {
            if(i2c_flag_get(i2c_periph, I2C_FLAG_BTC) != RESET)
            {
                break;
            }
        }

        if(timeout < I2C_BYTE_TIME_OUT)
            break;
    }

    channelx = (i2c_periph == I2C0) ? DMA_CH1 : DMA_CH3;
    vDMASetAddrAndLenthStart(channelx, DMA_MEMORY_TO_PERIPHERAL, (uint32_t)pBuffer, (uint32_t)&I2C_DATA(i2c_periph), iLength);

    /* 等待DMA传输完成 */
    while((dma_flag_get(channelx, DMA_FLAG_FTF) == RESET) && ((DMA_CHCTL(channelx) & DMA_CHXCTL_CHEN) != 0));
    dma_flag_clear(channelx, DMA_FLAG_FTF);

    /* wait until the stop condition is finished */
    for(timeout = 0; i2c_flag_get(i2c_periph, I2C_FLAG_BTC) == RESET; ++timeout)
    {
        if(timeout > (I2C_BYTE_TIME_OUT * iLength))
        {
            vI2CxBusReset();
            printf("cI2C %08X stpdet.\r\n", i2c_periph);
            return -5;
        }
    }

    /* send a stop condition to I2C bus */
    i2c_stop_on_bus(i2c_periph);
    /* i2c master sends STOP signal successfully */
    for(timeout = 0; timeout < I2C_BYTE_TIME_OUT; ++timeout)
    {
        if((I2C_CTL0(i2c_periph) & 0x0200) == RESET)
        {
            break;
        }
    }

    /* clear STPDET flag */
    i2c_flag_clear(i2c_periph, I2C_FLAG_STPDET);

    return 0;
}

/*!
    \brief      read data from the EEPROM
    \param[in]  p_buffer: pointer to the buffer that receives the data read from the EEPROM
    \param[in]  read_address: EEPROM's internal address to start reading from
    \param[in]  number_of_byte: number of bytes to reads from the EEPROM
    \param[out] none
    \retval     none
*/
int8_t cI2CxDMAReadDatas(uint32_t i2c_periph, uint16_t usIDAddr, uint16_t usRegAddr, uint8_t* pBuffer, int32_t iLength)
{
    void (*vI2CxBusReset)(void) = NULL;
    dma_channel_enum channelx;
    int32_t timeout = 0, startCnt = 0;

    if(iLength <= 0)
        return -1;

    vI2CxBusReset = (i2c_periph == I2C0) ? vIIC0DeInit : vIIC1DeInit;

    /* enable acknowledge */
    i2c_ack_config(i2c_periph, I2C_ACK_ENABLE);

    /* i2c master sends start signal only when the bus is idle */
    for(timeout = 0; i2c_flag_get(i2c_periph, I2C_FLAG_I2CBSY) != RESET; ++timeout)
    {
        if(timeout > I2C_BUSY_TIME_OUT)
        {
            vI2CxBusReset();
            printf("cI2C %08X busy.\r\n", i2c_periph);
            return -2;
        }
    }

    /* 重试 N 次 */
    for(startCnt = 0; startCnt < I2C_START_OUT; ++startCnt)
    {
        if(startCnt >= I2C_START_OUT)
        {
            vI2CxBusReset();
            printf("cI2C %08X start timeout.\r\n", i2c_periph);
            return -3;
        }

        i2c_start_on_bus(i2c_periph);
        /* i2c master sends START signal successfully */
        for(timeout = 0; timeout < I2C_BYTE_TIME_OUT; ++timeout)
        {
            if(i2c_flag_get(i2c_periph, I2C_FLAG_SBSEND) != RESET)
            {
                break;
            }
        }

        /* configure slave address */
        i2c_master_addressing(i2c_periph, usIDAddr, I2C_TRANSMITTER);
        /* address flag set means i2c slave sends ACK */
        for(timeout = 0; timeout < I2C_BYTE_TIME_OUT; ++timeout)
        {
            if(i2c_flag_get(i2c_periph, I2C_FLAG_ADDSEND) != RESET)
            {
                i2c_flag_clear(i2c_periph, I2C_FLAG_ADDSEND);
                break;
            }
        }

        /* wait until the transmit data buffer is empty */
        for(timeout = 0; timeout < I2C_BYTE_TIME_OUT; ++timeout)
        {
            if(i2c_flag_get(i2c_periph, I2C_FLAG_TBE) != RESET)
            {
                break;
            }
        }

        /* send address */
        i2c_data_transmit(i2c_periph, usRegAddr);
        /* wait until the transmit data buffer is empty */
        for(timeout = 0; timeout < I2C_BYTE_TIME_OUT; ++timeout)
        {
            if(i2c_flag_get(i2c_periph, I2C_FLAG_BTC) != RESET)
            {
                break;
            }
        }

        i2c_start_on_bus(i2c_periph);
        /* i2c master sends START signal successfully */
        for(timeout = 0; timeout < I2C_BYTE_TIME_OUT; ++timeout)
        {
            if(i2c_flag_get(i2c_periph, I2C_FLAG_SBSEND) != RESET)
            {
                break;
            }
        }

        /* configure slave address */
        i2c_master_addressing(i2c_periph, usIDAddr, I2C_RECEIVER);
        /* address flag set means i2c slave sends ACK */
        for(timeout = 0; timeout < I2C_BYTE_TIME_OUT; ++timeout)
        {
            if(i2c_flag_get(i2c_periph, I2C_FLAG_ADDSEND) != RESET)
            {
                i2c_flag_clear(i2c_periph, I2C_FLAG_ADDSEND);
                break;
            }
        }

        if(timeout < I2C_BYTE_TIME_OUT)
            break;
    }

    i2c_dma_last_transfer_config(i2c_periph, I2C_DMALST_ON);
    channelx = (i2c_periph == I2C0) ? DMA_CH2 : DMA_CH4;
    vDMASetAddrAndLenthStart(channelx, DMA_PERIPHERAL_TO_MEMORY, (uint32_t)pBuffer, (uint32_t)&I2C_DATA(i2c_periph), iLength);

    /* 等待DMA传输完成 */
    while((dma_flag_get(channelx, DMA_FLAG_FTF) == RESET) && ((DMA_CHCTL(channelx) & DMA_CHXCTL_CHEN) != 0));
    dma_flag_clear(channelx, DMA_FLAG_FTF);

    /* send a stop condition to I2C bus */
    i2c_stop_on_bus(i2c_periph);
    /* i2c master sends STOP signal successfully */
    for(timeout = 0; timeout < I2C_BYTE_TIME_OUT; ++timeout)
    {
        if((I2C_CTL0(i2c_periph) & 0x0200) == RESET)
        {
            break;
        }
    }

    return 0;
}
